//FACEBOOK ASYNC CLIENT CLASS IMPLEMENTATION

#include "facebookasync.h"

#include "xmlParser.h"
#include "md5.h"
#include <fstream>

//HELPER ROUTINES


void FacebookAsync::Helper_PrepareAPICall(const std::string& Method, std::list<std::string>& Params, FacebookAsyncUser* pUser)
{


	assert(pUser != NULL);
	CURL* pCurCurl = pUser->m_pCurl;
	assert(pCurCurl != NULL);

	//resetting curl state
	curl_easy_reset(pCurCurl);

	//setting url
	assert(m_ApiServer.size() > 0);
	assert(m_SecureApiServer.size() > 0);
	if (Method == "facebook.auth.getSession")
	{
		curl_easy_setopt(pCurCurl, CURLOPT_URL, m_SecureApiServer.c_str());
		curl_easy_setopt(pCurCurl, CURLOPT_SSL_VERIFYPEER, 0);
		curl_easy_setopt(pCurCurl, CURLOPT_SSL_VERIFYHOST, 0);
	}
	else
		curl_easy_setopt(pCurCurl, CURLOPT_URL, m_ApiServer.c_str());

	//adding standard params

	//callID - we just need to make sure that each callid is larger number than previous one
	m_CallID++;
	std::stringstream TempSStream;
	TempSStream << "call_id=" << m_CallID;
	Params.push_back(TempSStream.str());

	//api version we use
	std::string VersionNumber = "v=";
	VersionNumber.append(FACEBOOK_APIVER);
	Params.push_back(VersionNumber);

	//api key
	Params.push_back("api_key=" + m_AppKey);

	//method name as described in facebook reference documentation
	Params.push_back("method=" + Method);

	//session id
	if (pUser->m_LoginSession.size() > 0)
		Params.push_back("session_key=" + pUser->m_LoginSession);

	//generating and appending call signature
	std::string CallSig = "";
	HelperRequest_GetSignature(Params, CallSig, pUser); 
	Params.push_back("sig=" + CallSig);

	//generating params string
	pUser->m_BUFFER_ApiCallParams = "";
	HelperRequest_GetParamsString(Params, true, pUser->m_BUFFER_ApiCallParams);

	//setting POST params
	curl_easy_setopt(pCurCurl, CURLOPT_POST, true );
	curl_easy_setopt(pCurCurl, CURLOPT_POSTFIELDS, pUser->m_BUFFER_ApiCallParams.c_str());

	//setting write result callback
	pUser->m_CurRequestResult = "";
	curl_easy_setopt(pCurCurl, CURLOPT_WRITEFUNCTION, FacebookAsync::Write_Callback);
	curl_easy_setopt(pCurCurl, CURLOPT_WRITEDATA, &pUser->m_CurRequestResult);
	//curl_easy_setopt(pCurCurl, CURLOPT_VERBOSE, 1);
}



void FacebookAsync::Helper_PrepareAPICallEx(const std::string& Method, std::list<std::string>& Params, std::list<std::string>& NormalizedParams, FacebookAsyncUser* pUser)
{


	assert(pUser != NULL);
	CURL* pCurCurl = pUser->m_pCurl;
	assert(pCurCurl != NULL);

	//resetting curl state
	curl_easy_reset(pCurCurl);

	//setting url
	assert(m_ApiServer.size() > 0);
	assert(m_SecureApiServer.size() > 0);
	if (Method == "facebook.auth.getSession")
	{
		curl_easy_setopt(pCurCurl, CURLOPT_URL, m_SecureApiServer.c_str());
		curl_easy_setopt(pCurCurl, CURLOPT_SSL_VERIFYPEER, 0);
		curl_easy_setopt(pCurCurl, CURLOPT_SSL_VERIFYHOST, 0);
	}
	else
		curl_easy_setopt(pCurCurl, CURLOPT_URL, m_ApiServer.c_str());

	//adding standard params

	//callID - we just need to make sure that each callid is larger number than previous one
	m_CallID++;
	std::stringstream TempSStream;
	TempSStream << "call_id=" << m_CallID;
	Params.push_back(TempSStream.str());
	NormalizedParams.push_back(TempSStream.str());

	//api version we use
	std::string VersionNumber = "v=";
	VersionNumber.append(FACEBOOK_APIVER);
	Params.push_back(VersionNumber);
	NormalizedParams.push_back(VersionNumber);

	//api key
	Params.push_back("api_key=" + m_AppKey);
	NormalizedParams.push_back("api_key=" + m_AppKey);

	//method name as described in facebook reference documentation
	Params.push_back("method=" + Method);
	NormalizedParams.push_back("method=" + Method);


	//session id
	if (pUser->m_LoginSession.size() > 0)
	{
		Params.push_back("session_key=" + pUser->m_LoginSession);
		NormalizedParams.push_back("session_key=" + pUser->m_LoginSession);
	}

	//generating and appending call signature
	std::string CallSig = "";
	HelperRequest_GetSignature(Params, CallSig, pUser); 
	Params.push_back("sig=" + CallSig);
	NormalizedParams.push_back("sig=" + CallSig);


	//generating params string
	pUser->m_BUFFER_ApiCallParams = "";
	HelperRequest_GetParamsString(NormalizedParams, true, pUser->m_BUFFER_ApiCallParams);

	//setting POST params
	curl_easy_setopt(pCurCurl, CURLOPT_POST, true );
	curl_easy_setopt(pCurCurl, CURLOPT_POSTFIELDS, pUser->m_BUFFER_ApiCallParams.c_str());

	//setting write result callback
	pUser->m_CurRequestResult = "";
	curl_easy_setopt(pCurCurl, CURLOPT_WRITEFUNCTION, FacebookAsync::Write_Callback);
	curl_easy_setopt(pCurCurl, CURLOPT_WRITEDATA, &pUser->m_CurRequestResult);
	//curl_easy_setopt(pCurCurl, CURLOPT_VERBOSE, 1);



}
















void FacebookAsync::HelperRequest_MakeMD5(const std::string& SourceString, std::string& Result) 
{
	md5_state_t State;
	md5_byte_t Digest[16];
	char Hex_output[16*2 + 1];
	int di;
	md5_init( &State );
	md5_append( &State, (const md5_byte_t *) SourceString.c_str( ), (int) strlen( SourceString.c_str( ) ) );
	md5_finish( &State, Digest );
	for( di = 0; di < 16; ++di )
		sprintf( Hex_output+di * 2, "%02x", Digest[di] );
	
	Result = Hex_output;

}//void FacebookAsync::HelperRequest_MakeMD5(const std::string& SourceString, std::string& Result) 



void FacebookAsync::HelperRequest_GetParamsString(std::list<std::string>& Params, bool Separate, std::string& Result)
{

	std::list<std::string>::iterator Iter;
	
	Result = "";

	Params.sort();

	for(Iter = Params.begin(); Iter != Params.end(); Iter++)
	{
		Result.append(*Iter);
		if (Separate == true)
			Result.append("&");
	}
	if (Separate == true)
		Result.erase( Result.size() - 1, 1 );

}//void FacebookAsync::HelperRequest_GetParamsString(const std::list<std::string>& Params, bool Separate)


void FacebookAsync::HelperRequest_GetSignature(std::list<std::string>& Params, std::string& Result, FacebookAsyncUser* pUser)
{

	Result = "";

	assert(pUser != NULL);

	HelperRequest_GetParamsString(Params, false, Result);

	if(pUser->m_SessionSecret.size() > 0)
		Result.append(pUser->m_SessionSecret);
	else
		Result.append(m_AppSecret);

	HelperRequest_MakeMD5(Result, Result);

}//void FacebookAsync::HelperRequest_GetSignature(std::list<std::string>& Params, std::string& Result)


size_t FacebookAsync::Write_Callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
	std::string *str = (std::string *) userp;
	str->append( (char *) ptr, size*nmemb );
	return size*nmemb;
}//size_t FacebookAsync::Write_Callback(void *ptr, size_t size, size_t nmemb, void *userp)




void FacebookAsync::Helper_ParseProfileInfo(XMLNode& UserInfoNode, FacebookProfileInfo& Result)
{

	XMLNode CurNode;
	XMLNode ChildNode;

	//name
	CurNode = UserInfoNode.getChildNode(_T("name"));
	if (CurNode.getText() != NULL)
		Result.Name = CurNode.getText();

	//first_name
	CurNode = UserInfoNode.getChildNode(_T("first_name"));
	if (CurNode.getText() != NULL)
		Result.FirstName = CurNode.getText();

	//birthday
	CurNode = UserInfoNode.getChildNode(_T("birthday"));
	if (CurNode.getText() != NULL)
		Result.Birthday = CurNode.getText();

	//sex
	CurNode = UserInfoNode.getChildNode(_T("sex"));
	if (CurNode.getText() != NULL)
		Result.Sex = CurNode.getText();

	//is_app_user
	CurNode = UserInfoNode.getChildNode(_T("is_app_user"));
	if (CurNode.getText() != NULL)
		Result.IsAppUser = CurNode.getText();

	//status
	CurNode = UserInfoNode.getChildNode(_T("status"));
	ChildNode = CurNode.getChildNode(_T("message"));
	if (ChildNode.getText() != NULL)
		Result.Status = ChildNode.getText();

	//timezone
	CurNode = UserInfoNode.getChildNode(_T("timezone"));
	if (CurNode.getText() != NULL)
		Result.Timezone = CurNode.getText();

	//hometown_location: city and country
	CurNode = UserInfoNode.getChildNode(_T("hometown_location"));
	
	ChildNode = CurNode.getChildNode(_T("city"));
	if (ChildNode.getText() != NULL)
		Result.City = ChildNode.getText();

	ChildNode = CurNode.getChildNode(_T("country"));
	if (ChildNode.getText() != NULL)
		Result.Country = ChildNode.getText();

	//photo url
	Result.PhotoURL = "";

	CurNode = UserInfoNode.getChildNode(_T("pic"));
	if (CurNode.getText() != NULL)
	{
		Result.PhotoURL = CurNode.getText();
	}
	else
	{
		CurNode = UserInfoNode.getChildNode(_T("pic_small"));
		if (CurNode.getText() != NULL)
		{
			Result.PhotoURL = CurNode.getText();
		}
		else
		{
			CurNode = UserInfoNode.getChildNode(_T("pic_square"));
			if (CurNode.getText() != NULL)
			{
				Result.PhotoURL = CurNode.getText();
			}
			else
			{
				CurNode = UserInfoNode.getChildNode(_T("pic_big"));
				if (CurNode.getText() != NULL)
					Result.PhotoURL = CurNode.getText();
			}
		}
	}

	//photo size
	Result.PhotoSize.Width = 0;
	Result.PhotoSize.Height =0;

}//void FacebookAsync::Helper_ParseProfileInfo(XMLNode& UserInfoNode, FacebookProfileInfo& Result)



void FacebookAsync::Helper_PrepareGetPhotoSizeCall(FacebookAsyncUser* pUser, const std::string& PhotoURL)
{
	assert(PhotoURL.size() > 0);
	
	//creating curl easy handle
	CURL* pTempCurl = pUser->m_pCurl;

	//resetting the curl
	curl_easy_reset(pTempCurl);

	//providing url to curl
	pUser->m_BUFFER_PhotoGetSizeURL = FACEBOOK_IMAGESIZESCRIPTURL;
	pUser->m_BUFFER_PhotoGetSizeURL.append("?ImageURL=" + PhotoURL);
	curl_easy_setopt(pTempCurl, CURLOPT_URL, pUser->m_BUFFER_PhotoGetSizeURL.c_str());

	//telling curl it will be a GET request
	curl_easy_setopt(pTempCurl, CURLOPT_HTTPGET, true);

	//setting the request result writing
	pUser->m_CurRequestResult = "";

	curl_easy_setopt(pTempCurl, CURLOPT_WRITEFUNCTION, Facebook::Write_Callback);
    curl_easy_setopt(pTempCurl, CURLOPT_WRITEDATA, &pUser->m_CurRequestResult);	


}


void FacebookAsync::Helper_ParsePhotoSize(const std::string& RequestResult, FacebookPhotoSize& ResultSize)
{
	ResultSize.Width = 0;
	ResultSize.Height= 0;

	if (RequestResult.size() < 12)
	{
		//parsing result
		std::string::size_type FindAtLoc = RequestResult.find("@");

		if (FindAtLoc != std::string::npos)
		{
			std::string Width = RequestResult.substr(0, FindAtLoc);
			std::string Height= RequestResult.substr(FindAtLoc + 1, RequestResult.size() - FindAtLoc - 1);

			ResultSize.Width = atoi(Width.c_str());
			ResultSize.Height= atoi(Height.c_str());
		}
	}

}



void FacebookAsync::Helper_PrepareDownloadPhotoRequest(FacebookAsyncUser* pUser, const std::string& PhotoURL)
{

	//creating curl easy handle
	CURL* pTempCurl = pUser->m_pCurl;

	//resetting the curl
	curl_easy_reset(pTempCurl);

	//providing url to curl
	curl_easy_setopt(pTempCurl, CURLOPT_URL, PhotoURL.c_str());

	//telling curl it will be a GET request
	curl_easy_setopt(pTempCurl, CURLOPT_HTTPGET, true);

	//setting the request result writing
	pUser->m_CurRequestResult = "";

	curl_easy_setopt(pTempCurl, CURLOPT_WRITEFUNCTION, FacebookAsync::Write_Callback);
    curl_easy_setopt(pTempCurl, CURLOPT_WRITEDATA, &pUser->m_CurRequestResult);	

}







void FacebookAsync::Helper_BuildAppAndNonAppVectors(FacebookAsyncUser* pUser)
{

	assert(pUser != NULL);

	pUser->m_AppFriendsIndexes.clear();
	pUser->m_NonAppFriendsIndexes.clear();

	int FriendsCount = (int)pUser->m_FriendsProfileInfo.size();

	if (FriendsCount < 1)
		return;

	pUser->m_AppFriendsIndexes.reserve(FriendsCount);
	pUser->m_NonAppFriendsIndexes.reserve(FriendsCount);

	int i;

	for (i = 0; i < FriendsCount; i++)
	{
		if (pUser->m_FriendsProfileInfo.at(i).IsAppUser == "1")
			pUser->m_AppFriendsIndexes.push_back(i);
		else
			pUser->m_NonAppFriendsIndexes.push_back(i);
	}

}//void FacebookAsync::Helper_BuildAppAndNonAppVectors(FacebookUser* pUser)



void FacebookAsync::Helper_FillInfoFieldFromProfile(FACEBOOK_PROFILEINFO InfoField, const FacebookProfileInfo& ProfileInfo, std::string& Result)
{
	Result = "";

	switch(InfoField)
	{
	case FBPROFILEINFO_NAME:	  Result = ProfileInfo.Name; return;
	case FBPROFILEINFO_FIRSTNAME: Result = ProfileInfo.FirstName; return;
	case FBPROFILEINFO_BIRTHDAY:  Result = ProfileInfo.Birthday; return;
	case FBPROFILEINFO_SEX:		  Result = ProfileInfo.Sex; return;
	case FBPROFILEINFO_ISAPPUSER: Result = ProfileInfo.IsAppUser; return;
	case FBPROFILEINFO_STATUS:	  Result = ProfileInfo.Status; return;
	case FBPROFILEINFO_TIMEZONE:  Result = ProfileInfo.Timezone; return;
	case FBPROFILEINFO_CITY:	  Result = ProfileInfo.City; return;
	case FBPROFILEINFO_COUNTRY:   Result = ProfileInfo.Country; return;
	case FBPROFILEINFO_PHOTOURL:  Result = ProfileInfo.PhotoURL; return;
	}

}//void FacebookAsync::Helper_FillInfoFieldFromProfile(FACEBOOK_PROFILEINFO InfoField, const FacebookProfileInfo& ProfileInfo, std::string& Result)



void FacebookAsync::Helper_DecodeScriptedURL(const std::string& RequestResult, std::string& Result)
{
	Result = "";
	if (RequestResult.size() < 1)
		return;

	char tmpKav[2];
	tmpKav[0] = 34;
	tmpKav[1] = 0;

	std::string::size_type FirstKav = RequestResult.find(tmpKav);
	std::string::size_type SecondKav= RequestResult.find(tmpKav, FirstKav+1); 

	Result = RequestResult.substr(FirstKav+1, SecondKav - FirstKav - 1);

	std::string::size_type CurFind = Result.find("\\");

	while(CurFind != std::string::npos)
	{
		Result.erase(CurFind,1);
		CurFind = Result.find("\\");
	}

}




